package top.someapp.applib.generator;

import java.util.Random;
import java.util.UUID;

/**
 * id生成器
 *
 * @author zwz
 * Created on 2019-05-16
 */
public class IdGenerator {

    // 起始时间戳 1577836799999 (2020-01-01T07:59:59.999+08)
    private static final long startTimeStamp = 1577836799999L;
    private static final Random random = new Random();
    private static final Random random2 = new Random();
    private static int seq = 0;
    private static int rnd = random.nextInt(128);
    private static long lastTimeStamp = startTimeStamp;
    //    private static final long JS_SAFE_INTEGER = 0xFFFFFFFFFFFFFL; // 2 ^ 52
    private static int seq2 = 0;
    private static int rnd2 = 0;
    // JavaScript safe id
    private static long lastTimeStamp3 = startTimeStamp;
    private static final Random random3 = new Random();
    private static int seq3 = 0;
    private static int rnd3 = 1;

    /**
     * 获取随机UUID
     *
     * @return 随机UUID
     */
    public static String randomUUID() {
        return UUID.randomUUID().toString();
    }

    /**
     * 简化的UUID，去掉了横线
     *
     * @return 简化的UUID，去掉了分隔符
     */
    public static String simpleUUID() {
        return UUID.randomUUID().toString().replace('-', '\0');
    }

    /**
     * 参考Twitter的Snowflake 算法，修改的id生成算法<br>
     * Snowflake 原版算法如下：
     * <p>
     * 分布式系统中，有一些需要使用全局唯一ID的场景，有些时候我们希望能使用一种简单一些的ID，并且希望ID能够按照时间有序生成。
     * snowflake的结构如下(每部分用-分开):<br>
     *
     * <pre>
     *   0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000
     * </pre>
     * 第一位为未使用，接下来的41位为毫秒级时间(41位的长度可以使用69年)<br>
     * 然后是5位datacenterId和5位workerId(10位的长度最多支持部署1024个节点）<br>
     * 最后12位是毫秒内的计数（12位的计数顺序号支持每个节点每毫秒产生4096个ID序号）
     * 参考：http://www.cnblogs.com/relucent/p/4955340.html
     *
     * 因为系统时间可能会被回拨，所以需要加入随机值，避免在时间回拨后也不容易重复，具体格式为: <br>
     * 0 + 41位时间戳(从{@code startTimeStamp}开始计时) + 5位workerId(max:31) + 7位随机数(max:127) +
     * 10位序列号(max:1023)
     *
     * @param workerId machine id
     * @return next long id
     */
    public synchronized static long nextSnowflake(int workerId) {
        long now = System.currentTimeMillis();
        if (lastTimeStamp == now && seq == 1024) {
            for (now = System.currentTimeMillis(); lastTimeStamp == now; ) {
                now = System.currentTimeMillis(); // 等待时间流逝
            }
            rnd = random.nextInt(128);  // 重置随机值
            seq = 0; // 重置序列号
        }
        long mills = now - startTimeStamp;
        workerId &= 31;
//        System.out.println(String.format("mills=%d\tworkerId=%d\trnd=%d\tseq=%d",
//                mills, workerId, rnd, seq));
        long next = (mills << 22) | (workerId << 17) | (rnd << 10) | seq;
        lastTimeStamp = now;
        seq++;
        return next;
    }

    /**
     * 在JavaScript 内部，所有数字都是以64位浮点数形式储存，即使整数也是如此
     * 根据国际标准 IEEE 754，JavaScript 浮点数的64个二进制位，从最左边开始，是这样组成的。
     * <p>
     * 第1位：符号位，0表示正数，1表示负数 <br>
     * 第2位到第12位：指数部分 <br>
     * 第13位到第64位：小数部分（即有效数字）<br>
     * 精度最多只能到53个二进制位，这意味着，绝对值小于2的53次方的整数，即-(2^53 - 1)到2^53 - 1，都可以精确表示 <br>
     * 所以，大于等于2的53次方的数值，在JavaScript 中都无法精确表示。JavaScript(chrome)中有一个常量表示该值：<br>
     * Number.MAX_SAFE_INTEGER (9007199254740991) <br>
     * 为了在数据交换时(如json)能精确地表示整数值，定义此方法产生一个52 bits 范围内的id值 <br>
     * <p>
     * 0 + 41位时间戳(从{@code startTimeStamp}开始计时) + 2位随机数(max: 3) + 8位序列号(max:254)
     *
     * @return next int52 id
     */
    public synchronized static long nextInt52Id() {
        long now = System.currentTimeMillis();
        if (lastTimeStamp == now && seq2 == 256) {
            for (now = System.currentTimeMillis(); lastTimeStamp == now; ) {
                now = System.currentTimeMillis(); // 等待时间流逝
            }
            rnd2 = random2.nextInt(4);  // 重置随机值
            seq2 = 0; // 重置序列号
        }
        long mills = now - startTimeStamp;
        long next = (mills << 10) | (rnd2 << 8) | seq2;
        lastTimeStamp = now;
        seq2++;
        return next;
    }

    /**
     * 在JavaScript 内部，所有数字都是以64位浮点数形式储存，即使整数也是如此
     * 根据国际标准 IEEE 754，JavaScript 浮点数的64个二进制位，从最左边开始，是这样组成的。
     * <p>
     * 第1位：符号位，0表示正数，1表示负数 <br>
     * 第2位到第12位：指数部分 <br>
     * 第13位到第64位：小数部分（即有效数字）<br>
     * 精度最多只能到53个二进制位，这意味着，绝对值小于2的53次方的整数，即-(2^53 - 1)到2^53 - 1，都可以精确表示 <br>
     * 所以，大于等于2的53次方的数值，在JavaScript 中都无法精确表示。JavaScript(chrome)中有一个常量表示该值：<br>
     * Number.MAX_SAFE_INTEGER (9007199254740991) <br>
     * 为了在数据交换时(如json)能精确地表示整数值，定义此方法产生一个52 bits 范围内的id值 <br>
     * <p>
     * 0 + 41位时间戳(从{@code startTimeStamp}开始计时) + 3位随机数(max: 7) + 9位序列号(max:511)
     *
     * @return 在JavaScript safe id
     */
    public synchronized static long nextJsSafeId() {
        long now = System.currentTimeMillis();
        if (lastTimeStamp3 == now) {
            if (seq3 == 512) {
                rnd3 = random3.nextInt(8);  // 重置随机值
                seq3 = 0; // 重置序列号
                for (now = System.currentTimeMillis(); lastTimeStamp3 == now; ) {
                    now = System.currentTimeMillis(); // 等待时间流逝
                }
            }
        } else {
            rnd3 = random3.nextInt(8);  // 重置随机值
        }
        long mills = now - startTimeStamp;
        long next = (mills << 12) | (rnd3 << 9) | seq3;
        lastTimeStamp3 = now;
        seq3++;
        return next;
    }

}
